using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;


public class 寻路管理器_A星 : 单例_基础管理器<寻路管理器_A星>
{
    private Dictionary<Vector2Int, 寻路节点_A星> 节点容器;

    private Dictionary<节点类型_A星, List<Vector2Int>> 类型列表 = new Dictionary<节点类型_A星, List<Vector2Int>>();
    private Dictionary<节点类型_A星, List<Vector2Int>> 未处理类型列表 = new Dictionary<节点类型_A星, List<Vector2Int>>();
    private Dictionary<节点类型_A星, List<Vector2Int>> 完成处理类型列表 = new Dictionary<节点类型_A星, List<Vector2Int>>();

    public Dictionary<节点类型_A星, List<房间坐标类>> 房间列表 = new Dictionary<节点类型_A星, List<房间坐标类>>();

    public void 更改坐标类型(Vector2Int 坐标, 节点类型_A星 类型)
    {
        if (!节点容器.ContainsKey(坐标))
            return;

        节点容器[坐标].格子类型 = 类型; 
    }


    private Vector2Int 地图最小范围;//内部坐标
    private Vector2Int 地图最大范围;//内部坐标

    private Vector2Int 地图原点;//世界坐标

    public void 初始化地图信息(Vector2Int 地图宽高,Vector2Int 地图原点, Func<Vector2Int,节点类型_A星> 初始化数据委托)
    {
        节点容器 = new Dictionary<Vector2Int, 寻路节点_A星>();

        int 正(int a) => a % 2 == 1 ? a / 2 + 1 : a / 2;
        地图最小范围 = new Vector2Int(-地图宽高.x / 2, -地图宽高.y / 2);
        地图最大范围 = new Vector2Int(正(地图宽高.x), 正(地图宽高.y));

        for (int x = 地图最小范围.x; x < 地图最大范围.x; x++)
        {
            for (int y = 地图最小范围.y; y < 地图最大范围.y; y++)
            {
                Vector2Int pos = new Vector2Int(x, y);
                节点类型_A星 ls = 初始化数据委托(pos + 地图原点);
                if (类型列表.ContainsKey(ls))
                    类型列表[ls].Add(pos);
                else
                    类型列表.Add(ls, new List<Vector2Int>() { pos });

                节点容器.Add(pos,new 寻路节点_A星(pos, ls));
            }
        }

        foreach (var item in 类型列表)
            未处理类型列表.Add(item.Key,item.Value);
    }
    private Vector2Int[] 找到周围四个点(Vector2Int 坐标)
    {
        return new Vector2Int[4]
        {
                坐标+Vector2Int.up,
                坐标+Vector2Int.down,
                坐标+Vector2Int.left,
                坐标+Vector2Int.right,
        };
    }
    public void 计算房间(节点类型_A星 房间墙壁类型)//计算全图
    {
        if (!类型列表.ContainsKey(房间墙壁类型))
            return;

        
        // 已完成 随机找到墙体坐标 遍历周围的相连坐标 找到最大最小XY值 生成虚拟矩形区域 剔除墙体坐标 遍历相邻的地面坐标 剔除相邻为边界的坐标 剩下的坐标即为房间内部坐标 通过房间坐标找到墙体坐标

        List<Vector2Int> 相邻节点(寻路节点_A星 基点)
        {
            节点类型_A星[] 类型 = new 节点类型_A星[2] { 节点类型_A星.绝对障碍, 节点类型_A星.门 };
            if (!未处理类型列表.ContainsKey(类型[0]))
                return null;
            未处理类型列表[类型[0]].Remove(基点.坐标);
            未处理类型列表[类型[1]].Remove(基点.坐标);

            List<Vector2Int> 缓存 = new List<Vector2Int>();
            List<Vector2Int> 完成查找 = new List<Vector2Int>();

            Vector2Int 遍历点 = 基点.坐标;
            完成查找.Add(遍历点);

            while (true)//完成查找 得到所有相连节点
            {
                Vector2Int[] 周围点 = 找到周围四个点(遍历点);
                foreach (Vector2Int item in 周围点)
                {
                    if (完成查找.Contains(item))
                        continue;
                    //查看是否属于墙体
                    if (未处理类型列表[类型[0]].Contains(item) || 未处理类型列表[类型[1]].Contains(item))
                    {
                        //属于墙体
                        未处理类型列表[类型[0]].Remove(item);
                        未处理类型列表[类型[1]].Remove(item);
                        缓存.Add(item);
                    }//能够找到所有属于相同类型的相连节点
                }

                if (缓存.Count != 0)
                {
                    遍历点 = 缓存[0];
                    缓存.RemoveAt(0);
                    完成查找.Add(遍历点);
                }
                else
                    break;
            }

            return 完成查找;
        } //查找相连接的同类型坐标

        
       


        //以下为调用逻辑
        while (未处理类型列表[房间墙壁类型].Count != 0)
        {
            List<Vector2Int> 墙体坐标 = 相邻节点(节点容器[未处理类型列表[房间墙壁类型][0]]);
            (Vector2Int, Vector2Int) 最大最小值 = 找到最大最小值(墙体坐标.ToArray());
            List<Vector2Int> 地面坐标 = 剔除目标节点(最大最小值.Item1, 最大最小值.Item2, 墙体坐标);
            List<List<Vector2Int>> 房间地面坐标 = 得到房间地面坐标(最大最小值.Item1, 最大最小值.Item2, 地面坐标, 墙体坐标);
            Vector2Int 越界标识坐标 = new Vector2Int(999999, 999999);

            List<List<Vector2Int>> 越界集合 = new List<List<Vector2Int>>();
            foreach (var item in 房间地面坐标)
                if (item[item.Count - 1] == 越界标识坐标)//如果越界了 不要这一组数据
                    越界集合.Add(item);
            foreach (var item in 越界集合)
                房间地面坐标.Remove(item);

            if (!完成处理类型列表.ContainsKey(房间墙壁类型))
                完成处理类型列表.Add(房间墙壁类型, new List<Vector2Int>());

            foreach (var item in 墙体坐标)
                完成处理类型列表[房间墙壁类型].Add(item);


            foreach (var 房间坐标 in 房间地面坐标)
            {
                List<Vector2Int> 对应的墙体 = 通过房间地面坐标得到墙体坐标(房间坐标, 墙体坐标);
                房间坐标类 房间 = new 房间坐标类(房间坐标, 对应的墙体);

                foreach (var item in 房间坐标)
                    节点容器[item].所属房间 = 房间;
                foreach (var item in 对应的墙体)
                    节点容器[item].所属房间 = 房间;

                if (房间列表.ContainsKey(房间墙壁类型))
                    房间列表[房间墙壁类型].Add(房间);
                else
                    房间列表.Add(房间墙壁类型, new List<房间坐标类>() { 房间 });
            }
        }
    }

    public void 计算房间_后台(Action 完成后=null)
    {
        后台管理.获取实例.添加后台委托(() => 计算房间(节点类型_A星.绝对障碍), 完成后);
    }
    private (Vector2Int, Vector2Int) 找到最大最小值(Vector2Int[] 数组)
    {
        Vector2Int min = new Vector2Int(int.MaxValue, int.MaxValue);
        Vector2Int max = new Vector2Int(int.MinValue, int.MinValue);

        for (int i = 0; i < 数组.Length; i++)
        {
            min.x = min.x > 数组[i].x ? 数组[i].x : min.x;
            min.y = min.y > 数组[i].y ? 数组[i].y : min.y;
            max.x = max.x < 数组[i].x ? 数组[i].x : max.x;
            max.y = max.y < 数组[i].y ? 数组[i].y : max.y;
        }
        return (min, max);
    }
    private List<Vector2Int> 相邻节点(寻路节点_A星 基点)
    {
        节点类型_A星[] 类型 = new 节点类型_A星[2] { 节点类型_A星.绝对障碍, 节点类型_A星.门 };

        List<Vector2Int> 缓存 = new List<Vector2Int>();
        List<Vector2Int> 完成查找 = new List<Vector2Int>();


        Vector2Int 遍历点 = 基点.坐标;

        完成查找.Add(遍历点);

        while (true)//完成查找 得到所有相连节点
        {
            Vector2Int[] 周围点 = 找到周围四个点(遍历点);
            foreach (Vector2Int item in 周围点)
            {
                if (完成查找.Contains(item) || 缓存.Contains(item) || !节点容器.ContainsKey(item))
                    continue;
                //查看是否属于墙体
                if (节点容器[item].格子类型== 类型[0] || 节点容器[item].格子类型 ==类型[1])
                {
                    //属于墙体
                    缓存.Add(item);
                }//能够找到所有属于相同类型的相连节点
            }

            if (缓存.Count != 0)
            {
                遍历点 = 缓存[0];
                缓存.RemoveAt(0);
                完成查找.Add(遍历点);
            }
            else
                break;
        }

        return 完成查找;
    } //查找相连接的同类型坐标
    private List<Vector2Int> 剔除目标节点(Vector2Int Min, Vector2Int Max, List<Vector2Int> 目标节点)
    {
        List<Vector2Int> 返回值 = new List<Vector2Int>();
        for (int x = Min.x; x <= Max.x; x++)
        {
            for (int y = Min.y; y <= Max.y; y++)
            {
                Vector2Int ls = new Vector2Int(x, y);
                if (!目标节点.Contains(ls))
                    返回值.Add(ls);
            }
        }
        return 返回值;
    }
    private List<List<Vector2Int>> 得到房间地面坐标(Vector2Int Min, Vector2Int Max, List<Vector2Int> 地面坐标, List<Vector2Int> 墙体坐标)
    {
        List<Vector2Int> 缓存 = new List<Vector2Int>();
        List<List<Vector2Int>> 返回值 = new List<List<Vector2Int>>();


        List<Vector2Int> 查找容器(Vector2Int 处理, out bool 越界)
        {
            List<Vector2Int> 返回容器 = new List<Vector2Int>();
            越界 = false;//如果越界,返回空
            if (处理.x >= Max.x || 处理.x <= Min.x || 处理.y >= Max.y || 处理.y <= Min.y)
            {
                if (!墙体坐标.Contains(处理))
                    越界 = true;
            }
            返回容器.Add(处理);
            Vector2Int 坐标 = 处理;
            地面坐标.Remove(处理);

            越界 = 扩散添加到缓存()?true:越界;

            bool 扩散添加到缓存()
            {
                bool 越界 = false;
                Vector2Int[] 扩散 = 找到周围四个点(坐标);
                foreach (var item in 扩散)
                {
                    if (!地面坐标.Contains(item))//判断属于范围吗
                        continue;
                    //判断触碰边界 碰到了 直接清除容器
                    if (item.x >= Max.x || item.x <= Min.x || item.y >= Max.y || item.y <= Min.y)
                    {
                        if (!墙体坐标.Contains(item))
                            越界 = true;
                    }
                    缓存.Add(item);
                    地面坐标.Remove(item);
                }
                return 越界;
            }


            while (true)
            {
                if (缓存.Count == 0)
                {
                    return 返回容器;
                }

                坐标 = 缓存[0];
                缓存.RemoveAt(0);
                返回容器.Add(坐标);

                越界 = 扩散添加到缓存()?true:越界;
            }
        }
        while (地面坐标.Count != 0)
        {
            bool 越界了;
            List<Vector2Int> 房间坐标 = 查找容器(地面坐标[0],out 越界了);
            if (房间坐标 != null && !越界了) //没有触碰边界
                返回值.Add(房间坐标);
            else if(房间坐标 != null)
            {
                房间坐标.Add(new Vector2Int(999999, 999999));//这个坐标表示房间内部坐标越界了 ,后续用于检测 
                返回值.Add(房间坐标);
            }
        }
        return 返回值;
    }
    private List<Vector2Int> 通过房间地面坐标得到墙体坐标(List<Vector2Int> 房间坐标, List<Vector2Int> 墙体坐标)
    {
        // 遍历房间坐标 x+1 或y+1 x-1 y-1 找到重合的墙体坐标 找完全部返回即可 注意x+1 y+1 都有的情况下 两个都要检测重复 不重复既加入 
        List<Vector2Int> 返回 = new List<Vector2Int>();
        List<Vector2Int> 完成遍历 = new List<Vector2Int>();
        List<Vector2Int> 房间坐标_内部 = new List<Vector2Int>();
        List<Vector2Int> 缓存 = new List<Vector2Int>();

        foreach (var item in 房间坐标)
            房间坐标_内部.Add(item);

        Vector2Int 当前目标;
        if (房间坐标_内部.Count == 0)
            return null;
        else
            当前目标 = 房间坐标_内部[0];

        foreach (var item in 找到周围四个点(当前目标))
        {
            if (房间坐标_内部.Contains(item))
                缓存.Add(item);
        }

        完成遍历.Add(当前目标);

        while (true)
        {
            if (缓存.Count == 0)
                return 返回;


            foreach (var item in 找到周围四个点(当前目标))
            {
                if (墙体坐标.Contains(item))
                {
                    if (!返回.Contains(item))
                    {
                        返回.Add(item);
                        完成遍历.Add(item);
                    }
                }
            }

            当前目标 = 缓存[0];
            缓存.RemoveAt(0);

            foreach (var item in 找到周围四个点(当前目标))
            {
                if (房间坐标_内部.Contains(item) && !完成遍历.Contains(item))
                {
                    缓存.Add(item);
                    完成遍历.Add(item);
                }
            }
        }
    }
    /// <summary>
    /// 墙体强制为 绝对障碍类型
    /// </summary>
    /// <param name="坐标"></param>
    public void 计算房间_局部(Vector2Int 坐标)
    {
        //坐标 不是墙体 就是 地面
        //如果是墙体 直接找到 房间字典 
        //如果 是 地面 找到房间字典 墙体有没有他

        if (!节点容器.ContainsKey(坐标))
            return;

        节点类型_A星 类型 = 节点容器[坐标].格子类型;

        if (类型 == 节点类型_A星.绝对障碍)
        {
            //是墙体 建造了墙体
            List<Vector2Int> 墙体相连坐标 = 相邻节点(节点容器[坐标]);
            (Vector2Int, Vector2Int) 最小与最大 = 找到最大最小值(墙体相连坐标.ToArray());
            List<Vector2Int> 地面 = 剔除目标节点(最小与最大.Item1, 最小与最大.Item2, 墙体相连坐标);
            List<List<Vector2Int>> 房间地面 = 得到房间地面坐标(最小与最大.Item1, 最小与最大.Item2, 地面, 墙体相连坐标);
            Vector2Int 越界标识 = new Vector2Int(999999, 999999);

            List<List<Vector2Int>> 越界集合 = new List<List<Vector2Int>>();
            foreach (var item in 房间地面)
                if (item[item.Count - 1] == 越界标识)//如果越界了 不要这一组数据
                    越界集合.Add(item);
            foreach (var item in 越界集合)
                房间地面.Remove(item);


            foreach (var item in 房间地面)
            {
                List<Vector2Int> 新墙体坐标 = 通过房间地面坐标得到墙体坐标(item, 墙体相连坐标);
                if (新墙体坐标.Count < 4)
                    continue;

                房间坐标类 新房间 = new 房间坐标类(item, 新墙体坐标);
                foreach (var 墙体坐标 in 新墙体坐标)
                    节点容器[墙体坐标].所属房间 = 新房间;
                foreach (var 地面坐标 in item)
                    节点容器[地面坐标].所属房间 = 新房间;

                List<房间坐标类> 移除列表 = new List<房间坐标类>();
                foreach (var 房间列表_ in 房间列表[节点类型_A星.绝对障碍])
                    if (房间列表_.坐标是否属于房间(item))
                        移除列表.Add(房间列表_);

                foreach (var 移除 in 移除列表)
                    房间列表[节点类型_A星.绝对障碍].Remove(移除);

                房间列表[节点类型_A星.绝对障碍].Add(新房间);
            }
        }
        else
        {
            //非墙体 移除了墙体

            //检验房间完整性
            房间坐标类 房间 = 节点容器[坐标].所属房间;

            if (房间 == null)
                return;

            if (房间.墙体坐标.Count >= 1)
                房间.墙体坐标.Remove(坐标);

            if (房间.墙体坐标.Count == 0)
            {
                房间列表[节点类型_A星.绝对障碍].Remove(房间);
                房间.移除房间(节点容器);
                return;
            }

            List<Vector2Int> 墙体相连坐标 = 相邻节点(节点容器[房间.墙体坐标[0]]);
            (Vector2Int, Vector2Int) 最小与最大 = 找到最大最小值(墙体相连坐标.ToArray());
            List<Vector2Int> 地面 = 剔除目标节点(最小与最大.Item1, 最小与最大.Item2, 墙体相连坐标);
            
            List<List<Vector2Int>> 房间地面 = 得到房间地面坐标(最小与最大.Item1, 最小与最大.Item2, 地面, 墙体相连坐标);
            if (房间地面.Count==0)
            {
                //房间被解除
                房间.移除房间(节点容器);
                房间列表[节点类型_A星.绝对障碍].Remove(房间);
                节点容器[坐标].所属房间 = null;
            }
            Vector2Int 越界标识 = new Vector2Int(999999, 999999);
            List<List<Vector2Int>> 越界移除 = new List<List<Vector2Int>>();
            foreach (var item in 房间地面)//检测发生越界行为后 移除原本存在的房间
            {
                if(item[item.Count-1]== 越界标识)
                {
                    item.RemoveAt(item.Count - 1);//剩下的是越界坐标
                    越界移除.Add(item);
                    foreach (var 房间地面坐标_越界 in item)
                    {
                        if (节点容器.ContainsKey(房间地面坐标_越界))
                        {
                            寻路节点_A星 节点 = 节点容器[房间地面坐标_越界];
                            if (节点.所属房间 != null)
                            {
                                房间坐标类 房间类 = 节点.所属房间;
                                房间类.移除房间(节点容器);
                                房间列表[节点类型_A星.绝对障碍].Remove(节点.所属房间);
                            }
                        }
                    }
                }
            }
            foreach (var item in 越界移除)
                房间地面.Remove(item);

            foreach (var item in 房间地面)
            {
                List<Vector2Int> 新墙体坐标 = 通过房间地面坐标得到墙体坐标(item, 墙体相连坐标);
                if (新墙体坐标.Count >= 4)
                {
                    房间坐标类 新房间 = new 房间坐标类(item, 新墙体坐标);
                    foreach (var 墙体坐标 in 新墙体坐标)
                        节点容器[墙体坐标].所属房间 = 新房间;
                    foreach (var 地面坐标 in item)
                        节点容器[地面坐标].所属房间 = 新房间;

                    List<房间坐标类> 移除列表 = new List<房间坐标类>();
                    foreach (var 房间列表_ in 房间列表[节点类型_A星.绝对障碍])
                        if (房间列表_.坐标是否属于房间(item))
                            移除列表.Add(房间列表_);

                    foreach (var 移除 in 移除列表)
                        房间列表[节点类型_A星.绝对障碍].Remove(移除);

                    房间列表[节点类型_A星.绝对障碍].Add(新房间);
                }
            }
        }
    }

    public void 计算房间_局部_后台(Vector2Int 坐标, Action 完成后=null)
    {
        后台管理.获取实例.添加后台委托(() => 计算房间_局部(坐标), 完成后);
    }

    public void 初始化地图信息_后台(Vector2Int 地图宽高, Vector2Int 地图原点, Func<Vector2Int, 节点类型_A星> 初始化数据委托,Action 创建完成回调=null)
    {
        后台管理.获取实例.添加后台委托(() => 初始化地图信息(地图宽高, 地图原点, 初始化数据委托), 创建完成回调);
    }

    public void 设置节点类型(Vector2Int 世界坐标, 节点类型_A星 类型)
    {
        Vector2Int 内部坐标 = 世界坐标 - 地图原点;
        if (节点容器.ContainsKey(内部坐标))
            节点容器[内部坐标].格子类型 = 类型;
        else
            Debug.LogWarning("A星坐标" + 世界坐标 + "不存在");
    }
    public void 设置节点类型_自动计算房间(Vector2Int 世界坐标, 节点类型_A星 类型,Action 完成后回调=null)
    {
        Vector2Int 内部坐标 = 世界坐标 - 地图原点;
        设置节点类型(世界坐标, 类型);
        计算房间_局部_后台(内部坐标, 完成后回调);
    }
    /// <summary>
    /// 寻找最佳路径
    /// </summary>
    /// <param name="自身坐标">可以是世界坐标</param>
    /// <param name="目标坐标">可以是世界坐标</param>
    /// <param name="阻挡类型">障碍物(墙体) 所属类型</param>
    /// <returns></returns>
    public List<寻路节点_A星> 寻路(Vector2Int 自身坐标,Vector2Int 目标坐标,节点类型_A星[] 阻挡类型)
    {
        List<寻路节点_A星> 开启列表 = new List<寻路节点_A星>();
        List<寻路节点_A星> 关闭列表 = new List<寻路节点_A星>();
        if (节点容器 == null)
        {
            Debug.LogError("请先初始化A*寻路");
            return null;
        }
        Vector2Int 自身内部坐标 = 自身坐标 - 地图原点;
        Vector2Int 目标内部坐标 = 目标坐标 - 地图原点;
        bool 目标是障碍物 = false;

        //判断是否在地图之外
        //如果自身坐标在地图之外则返回为空
        if (!节点容器.ContainsKey(自身内部坐标))
            return null;

        //如果自身坐标为阻挡
        寻路节点_A星 自身 = 节点容器[自身内部坐标];
        foreach (var item in 阻挡类型)
            if (自身.格子类型 == item)
                return null;//卡墙里了 不予计算

        //判断目标地点是否在边界之外 如果是 继续运行 但是目标将改为边界点
        目标内部坐标.x = Mathf.Clamp(目标内部坐标.x, 地图最小范围.x, 地图最大范围.x-1);
        目标内部坐标.y = Mathf.Clamp(目标内部坐标.y, 地图最小范围.y, 地图最大范围.y-1);

        //判断自身是否在封闭房间内
        bool 自身在房间 = false;
        if (房间列表.ContainsKey(节点类型_A星.绝对障碍))
        {
            foreach (var item in 房间列表[节点类型_A星.绝对障碍])
            {
                if (item.坐标是否属于房间(自身内部坐标))
                {
                    自身在房间 = true;
                    break;
                }
            }
        }

        List<Vector2Int> 房间地面坐标 = new List<Vector2Int>();//防止重置目标进入内部 算法白费

        //判断目标是否在房间内
        if (房间列表.ContainsKey(节点类型_A星.绝对障碍) && !自身在房间 && 阻挡类型.Contains(节点类型_A星.门))
        {
            foreach (var item in 房间列表[节点类型_A星.绝对障碍])
            {
                if (item.坐标是否属于房间(目标内部坐标))
                {
                    Vector2Int 新 = item.最近点墙壁(目标内部坐标);
                    if (新 != null)
                    {
                        目标内部坐标 = 新;
                        房间地面坐标 = item.地面坐标;
                    }
                    break;
                }
            }
        }

        //判断目标是否在障碍物中
        寻路节点_A星 目标 = 节点容器[目标内部坐标];
        foreach (var item in 阻挡类型)
            if (目标.格子类型 == item){
                目标是障碍物 = true;
                break;
            }
        

        //清空开启关闭列表
        开启列表.Clear();
        关闭列表.Clear();

        //首先把自身节点加入关闭列表中
        自身.父对象 = null;
        自身.清空寻路消耗参数();
        关闭列表.Add(自身);

        // BUG!!! 当目标是障碍物时 进入该处理 然后死循环 ***********已解决
        if (目标是障碍物)
        {
            Vector2Int 方向 = 目标内部坐标 - 自身内部坐标;

            约束();
            void 约束()
            {
                方向.x = Mathf.Clamp(方向.x, -1, 1);
                方向.y = Mathf.Clamp(方向.y, -1, 1);
            }

            //得到近点坐标
            Vector2Int 预设目标 = 目标内部坐标;
            while (true)
            {
                方向 = 预设目标 - 自身内部坐标;
                约束();
                预设目标 -= 方向;
                if (节点容器.ContainsKey(预设目标))
                {
                    bool 阻挡 = false;
                    foreach (var item in 阻挡类型)
                        if (item == 节点容器[预设目标].格子类型 || 房间地面坐标.Contains(预设目标))
                        {
                            阻挡 = true;
                            break;
                        }
                    if (!阻挡)
                        break;
                }
                else
                    return null;
            }
            目标内部坐标 = 预设目标;
        }

        int 等于0 = 0;
        while (true)
        {
            if (开启列表.Count == 0 && 等于0 >1)
            {
                关闭列表.OrderByDescending(t => t.寻路消耗);

                //既然是死路那就找到与目标最近的点
                //目标找到最近的点并赋值到自身

                float 最近距离 = float.MaxValue;

                for (int i = 0; i < 关闭列表.Count; i++)
                {
                    float 距离 = Vector2Int.Distance(关闭列表[i].坐标, 目标内部坐标);
                    if (距离 < 最近距离)
                    {
                        最近距离 = 距离;
                        自身 = 关闭列表[i];
                    }
                }

                List<寻路节点_A星> 返回 = new List<寻路节点_A星>();
                while (自身.父对象 != null)
                {
                    返回.Add(自身);
                    自身 = 自身.父对象;
                }
                返回.Reverse();
                return 返回;
            }

            //第一次开启列表必定为0
            if (开启列表.Count == 0)
            {
                等于0++;
                //找到起点周围的点 8个 加入开启列表
                找到周围的点放入开启列表中(自身.坐标, 阻挡类型, 节点容器[目标内部坐标]);
                continue;
            }


            //找到寻路消耗最小的点
            //放入关闭列表 并从 开启列表 移除
            自身 = 开启列表[0];
            foreach (var item in 开启列表)
                if (item.寻路消耗 < 自身.寻路消耗)
                    自身 = item;

            关闭列表.Add(自身);
            开启列表.Remove(自身);


            //找到起点周围的点 8个 加入开启列表
            找到周围的点放入开启列表中(自身.坐标, 阻挡类型, 节点容器[目标内部坐标]);

            //如果这个点已经是终点的那么返回
            //否则继续寻路
            if (自身 == 节点容器[目标内部坐标])
            {
                //找完了
                List<寻路节点_A星> 返回 = new List<寻路节点_A星>();
                while (自身.父对象!=null)
                {
                    返回.Add(自身);
                    自身 = 自身.父对象;
                }
                返回.Reverse();
                return 返回;
            }
        }

        void 找到周围的点放入开启列表中(Vector2Int 内部坐标, 节点类型_A星[] 阻挡类型, 寻路节点_A星 终点)
        {
            Vector2Int[] 正边 = new Vector2Int[4]
            {
            new Vector2Int(内部坐标.x-1,内部坐标.y),//左边
            new Vector2Int(内部坐标.x+1,内部坐标.y),//右边
            new Vector2Int(内部坐标.x,内部坐标.y-1),//下面
            new Vector2Int(内部坐标.x,内部坐标.y+1),//上面
            };
            Vector2Int[] 斜边 = new Vector2Int[4]
            {
            new Vector2Int(内部坐标.x+1,内部坐标.y+1),//左上
            new Vector2Int(内部坐标.x-1,内部坐标.y+1),//右上
            new Vector2Int(内部坐标.x+1,内部坐标.y-1),//右下
            new Vector2Int(内部坐标.x-1,内部坐标.y-1),//左下
            };
            bool 上, 下, 左, 右;//方向是否有阻碍
            上 = 将坐标放入开启列表中(正边[3], 阻挡类型, 节点容器[内部坐标], 终点, 1);
            下 = 将坐标放入开启列表中(正边[2], 阻挡类型, 节点容器[内部坐标], 终点, 1);
            左 = 将坐标放入开启列表中(正边[0], 阻挡类型, 节点容器[内部坐标], 终点, 1);
            右 = 将坐标放入开启列表中(正边[1], 阻挡类型, 节点容器[内部坐标], 终点, 1);

            //如果要向右上走 必须 上方和右方没有障碍物
            //如果要向左上走 必须 上方和左方没有障碍物
            if (!上 && !右)//右上
                将坐标放入开启列表中(斜边[1], 阻挡类型, 节点容器[内部坐标], 终点, 1.4f);
            if (!上 && !左)//左上
                将坐标放入开启列表中(斜边[0], 阻挡类型, 节点容器[内部坐标], 终点, 1.4f);
            if (!下 && !左)//左下
                将坐标放入开启列表中(斜边[3], 阻挡类型, 节点容器[内部坐标], 终点, 1.4f);
            if (!下 && !右)//右下
                将坐标放入开启列表中(斜边[2], 阻挡类型, 节点容器[内部坐标], 终点, 1.4f);

        }
        bool 将坐标放入开启列表中(Vector2Int pos, 节点类型_A星[] 阻挡类型, 寻路节点_A星 父对象, 寻路节点_A星 终点, float 距离上一步距离)
        {
            if (!节点容器.ContainsKey(pos))//如果超出范围
                return false;
            if (开启列表.Contains(节点容器[pos]) || 关闭列表.Contains(节点容器[pos]))//判断是否在开启关闭列表
                return false;
            foreach (var item in 阻挡类型)//判断是否被阻挡
                if (节点容器[pos].格子类型 == item)
                    return true;
            //计算寻路消耗
            //斜边1.4 
            寻路节点_A星 当前节点 = 节点容器[pos];
            当前节点.父对象 = 父对象;
            //计算 当前距离起点的距离
            当前节点.距离起点的距离 = 当前节点.父对象.距离起点的距离 + 距离上一步距离;
            当前节点.距离终点的距离 = Mathf.Abs(终点.坐标.x - 当前节点.坐标.x) + Mathf.Abs(终点.坐标.y - 当前节点.坐标.y);
            当前节点.寻路消耗 = 当前节点.距离起点的距离 + 当前节点.距离终点的距离;

            开启列表.Add(节点容器[pos]);
            return false;
        }

    }
    /// <summary>
    /// 重要说明: 当阻挡类型只有墙壁没有门时 导航会通过门进出房间
    /// </summary>
    /// <param name="自身坐标"></param>
    /// <param name="目标坐标"></param>
    /// <param name="阻挡类型"></param>
    /// <param name="回调">寻找完成后要做的事情</param>
    public void 寻路_后台(Vector2Int 自身坐标, Vector2Int 目标坐标, 节点类型_A星[] 阻挡类型,Action<List<寻路节点_A星>> 回调)
    {
        后台管理.获取实例.添加后台委托(() => 寻路(自身坐标, 目标坐标, 阻挡类型), 回调);
    }
}
